Open
Conversation
The continue statement only broke out of the inner loop, so loopback/local IPs (e.g. 127.0.0.1) were never filtered. This caused ctrld to use itself as bootstrap DNS when already installed as the system resolver — a self-referential loop. Use the same isLocal flag pattern as getDNSFromScutil() and getAllDHCPNameservers().
Add defer windows.CloseHandle(h) after CreateToolhelp32Snapshot to ensure the process snapshot handle is properly released on all code paths (match found, enumeration exhausted, or error).
This commit adds a new `ctrld log tail` subcommand that streams runtime debug logs to the terminal in real-time, similar to `tail -f`. Changes: - log_writer.go: Add Subscribe/tailLastLines for fan-out to tail clients - control_server.go: Add /log/tail endpoint with streaming response - Internal logging: subscribes to logWriter for live data - File-based logging: polls log file for new data (200ms interval) - Sends last N lines as initial context on connect - commands.go: Add `log tail` cobra subcommand with --lines/-n flag - control_client.go: Add postStream() with no timeout for long-lived connections Usage: sudo ctrld log tail # shows last 10 lines then follows sudo ctrld log tail -n 50 # shows last 50 lines then follows Ctrl+C to stop
upstreamConfigFor() used strings.Contains(":") to detect whether to
append ":53", but IPv6 addresses contain colons, so IPv6 servers were
passed as bare addresses (e.g. "2a0d:6fc0:9b0:3600::1") to net.Dial
which rejects them with "too many colons in address".
Use net.JoinHostPort() which handles both IPv4 and IPv6 correctly,
producing "[2a0d:6fc0:9b0:3600::1]:53" for IPv6.
macOS rejects sendmsg from [::1] to global unicast IPv6 (EINVAL), and nat on lo0 doesn't fire for route-to'd packets (pf skips translation on the second interface pass). ULA addresses on lo0 also fail (EHOSTUNREACH - kernel segregates lo0 routing). Solution: wrap the [::1] UDP listener's ResponseWriter with rawIPv6Writer that sends responses via SOCK_RAW (IPPROTO_UDP) on lo0, bypassing the kernel's routing validation. pf's rdr state reverses the address translation on the response path. Changes: - Add rawipv6_darwin.go: rawIPv6Writer wraps dns.ResponseWriter, sends UDP responses via raw IPv6 socket with proper checksum calculation - Add rawipv6_other.go: no-op wrapIPv6Handler for non-darwin platforms - Remove nat rules from pf anchor (no longer needed) - Block IPv6 TCP DNS (block return) - falls back to IPv4 (~1s, rare) - Remove IPv6 TCP rdr/route-to/pass rules (only UDP intercepted)
…rn type The handler variable is dns.HandlerFunc but wrapIPv6Handler returns dns.Handler (interface). Go's type inference picked dns.HandlerFunc for ipv6Handler, causing a compile error on assignment. Explicit type declaration fixes the mismatch.
IPv6 DNS interception on macOS is not feasible with current pf capabilities.
The kernel rejects sendmsg from [::1] to global unicast (EINVAL), nat on lo0
doesn't fire for route-to'd packets, raw sockets bypass routing but pf doesn't
match them against rdr state, and DIOCNATLOOK can't be used because bind()
fails for non-local addresses.
Replace all IPv6 interception code with a simple pf block rule:
block out quick on ! lo0 inet6 proto { udp, tcp } from any to any port 53
macOS automatically retries DNS over IPv4 when IPv6 is blocked.
Changes:
- Remove rawipv6_darwin.go and rawipv6_other.go
- Remove [::1] listener spawn on macOS (needLocalIPv6Listener returns false)
- Remove IPv6 rdr, route-to, pass, and reply-to pf rules
- Add block rule for all outbound IPv6 DNS
- Update docs/pf-dns-intercept.md with what was tried and why it failed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Minor Release
This contains bug fixes and a new diagnostic command.
Added
ctrld log tailcommand for live log streaming — streams runtime debug logs to the terminal in real-time, similar totail -f. Supports--lines/-nflag to control initial context linesFixed
Fixed handle leak in
hasLocalDnsServerRunning()on Windows — the process snapshot handle fromCreateToolhelp32Snapshotwas not being closed, leaking a handle on every callFixed
dnsFromResolvConfnot filtering loopback IPs — thecontinuestatement only broke out of the inner loop, allowing loopback addresses (e.g. 127.0.0.1) through. This caused ctrld to use itself as bootstrap DNS when already installed as the system resolver, creating a self-referential loopFixed IPv6 VPN DNS server addresses not formatted correctly on macOS —
upstreamConfigFor()passed bare IPv6 addresses tonet.Dialwithout brackets or port, causingtoo many colons in addresserrors and immediate failure for all IPv6 VPN DNS queriesFixed DNS responses failing with
sendmsg: invalid argumentfor IPv6-sourced clients on macOS — the pfnatrule onlo0 inet6did not match packets arriving via therdrchain as inet4, so the client's global IPv6 source address was preserved, and the kernel rejected responses from[::1]:53to non-loopback destinationsFixed VPN DNS queries routed over wrong source interface on macOS — when a VPN client (e.g. FortiClient) was active, DNS queries to LAN servers used the VPN tunnel IP as source, making responses unroutable. Combined with the IPv6 bugs, this cascaded into complete VPN DNS failure and VPN disconnection